{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ANN hyperparameter evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "\n",
    "src_dir = os.path.abspath('../src')\n",
    "if src_dir not in sys.path:\n",
    "    sys.path.append(src_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "\n",
    "import itertools\n",
    "import logging\n",
    "import multiprocessing\n",
    "import re\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.ticker as mticker\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import tqdm\n",
    "\n",
    "from ann_solo import reader, spectral_library\n",
    "from ann_solo.config import config"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "tqdm.tqdm = tqdm.tqdm_notebook\n",
    "\n",
    "# plot styling\n",
    "plt.style.use(['seaborn-white', 'seaborn-paper'])\n",
    "plt.rc('font', family='serif')\n",
    "sns.set_palette('Set1')\n",
    "sns.set_context('paper', font_scale=1.3)    # single-column figure\n",
    "\n",
    "# initialize logging\n",
    "logging.basicConfig(format='%(asctime)s [%(levelname)s/%(processName)s] '\n",
    "                           '%(module)s.%(funcName)s : %(message)s',\n",
    "                    level=logging.INFO)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "config_filename = '../../bin/ann-solo/iprg2012.ini'\n",
    "splib_filename = '../../data/interim/iprg2012/human_yeast_targetdecoy.splib'\n",
    "mgf_filename = '../../data/external/iprg2012/iPRG2012.mgf'\n",
    "hp_dir = '../../data/processed/iprg2012/ann_hyperparameters'\n",
    "build_dir = '../../data/processed/iprg2012/build_ann'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We consider the following hyperparameters for approximate nearest neighbor (ANN) searching:\n",
    "\n",
    "- The number of lists in the ANN index (`num_list`).\n",
    "- The number of lists in the ANN index to inspect during the search (`num_probe`).\n",
    "\n",
    "Both of these hyperparameters provide a trade-off between speed and accuracy, either at build time or at run time.\n",
    "\n",
    "For more information about the hyperparameters, please see the [Faiss](https://github.com/facebookresearch/faiss) library.\n",
    "\n",
    "For each combination of hyperparameters the trade-off between search speed (total time required) and accuracy (number of PSMs at 1% FDR) is evaluated.\n",
    "The number of identifications for a closed search (precursor mass = 20 ppm) and a traditional, brute-force, open modification search are indicated as a reference.\n",
    "\n",
    "Search settings:\n",
    "\n",
    "- Query file: spectra generated for the [iPRG 2012 study](http://www.mcponline.org/cgi/doi/10.1074/mcp.M113.032813).\n",
    "- Precursor mass tolerance: 300 Da\n",
    "- Fragment mass tolerance: 0.02 Da"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _do_search(settings):\n",
    "    config.parse(settings)\n",
    "\n",
    "    spec_lib = spectral_library.SpectralLibrary(\n",
    "        config.spectral_library_filename)\n",
    "    identifications = spec_lib.search(config.query_filename)\n",
    "    writer.write_mztab(identifications, config.out_filename,\n",
    "                       spec_lib._library_reader)\n",
    "    spec_lib.shutdown()\n",
    "\n",
    "# Go through all ANN hyperparameter combinations.\n",
    "num_list_options = [64, 256, 1024, 4096, 16384]\n",
    "num_probe_options = [1, 8, 32, 64, 128, 256, 512, 1024]\n",
    "for num_list, num_probe in itertools.product(num_list_options,\n",
    "                                             num_probe_options):\n",
    "    if num_list > num_probe:\n",
    "        out_filename = os.path.join(\n",
    "            hp_dir, f'num_list_{num_list}-num_probe_{num_probe}.mztab')\n",
    "        if not os.path.isfile(out_filename):\n",
    "            settings = [f'--config {config_filename}',\n",
    "                        '--precursor_tolerance_mass_open 300',\n",
    "                        '--precursor_tolerance_mode_open Da',\n",
    "                        '--mode ann',\n",
    "                        f'--num_list {num_list}',\n",
    "                        f'--num_probe {num_probe}',\n",
    "                        splib_filename, mgf_filename, out_filename]\n",
    "\n",
    "            proc = multiprocessing.Process(target=_do_search,\n",
    "                                           args=(' '.join(settings),))\n",
    "            proc.start()\n",
    "            proc.join()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def extract_time_from_log(filename):\n",
    "    with open(filename, 'r') as f_in:\n",
    "        for line in f_in:\n",
    "            if line.startswith('real'):\n",
    "                # Wall clock time.\n",
    "                realtime = line.split()[1]\n",
    "                minutes = int(realtime[:realtime.find('m')])\n",
    "                seconds = float(realtime[realtime.find('m') + 1:\n",
    "                                         realtime.rfind('s')])\n",
    "                realtime = minutes * 60 + seconds\n",
    "                \n",
    "                return realtime"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6504e4737be748b2a61d73454a7b59f6",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "HBox(children=(IntProgress(value=0, description='Files processed', max=125, style=ProgressStyle(description_wi…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "pattern_hyperparameters_ssms = re.compile(\n",
    "    '^num_list_(\\d+)-num_probe_(\\d+)(-no_gpu)?.mztab$')\n",
    "pattern_hyperparameters_log = re.compile(\n",
    "    '^num_list_(\\d+)-num_probe_(\\d+)(-no_gpu)?.log$')\n",
    "\n",
    "ssms = []\n",
    "runtimes = []\n",
    "for filename in tqdm.tqdm(os.listdir(hp_dir), 'Files processed',\n",
    "                          unit='files'):\n",
    "    match_ssms = pattern_hyperparameters_ssms.match(filename)\n",
    "    match_log = pattern_hyperparameters_log.match(filename)\n",
    "    if match_ssms is not None:\n",
    "        ssms.append((\n",
    "            int(match_ssms.group(1)), int(match_ssms.group(2)),\n",
    "            len(reader.read_mztab_ssms(os.path.join(hp_dir, filename))),\n",
    "            match_ssms.group(3) is None))\n",
    "    if match_log is not None:\n",
    "        runtimes.append((\n",
    "            int(match_log.group(1)), int(match_log.group(2)),\n",
    "            extract_time_from_log(os.path.join(hp_dir, filename)),\n",
    "            match_log.group(3) is None))\n",
    "\n",
    "ssms_df = pd.DataFrame.from_records(\n",
    "    ssms, columns=['num_list', 'num_probe', 'ssms', 'gpu'])\n",
    "time_df = pd.DataFrame.from_records(\n",
    "    runtimes, columns=['num_list', 'num_probe', 'time', 'gpu'])\n",
    "hyperparameters = pd.merge(ssms_df, time_df,\n",
    "                           on=['num_list', 'num_probe', 'gpu'])\n",
    "hyperparameters['mode'] = 'ANN-SoLo'\n",
    "hyperparameters['precursor_tol_mass'] = 300\n",
    "hyperparameters['precursor_tol_mode'] = 'Da'\n",
    "\n",
    "hyperparameters = hyperparameters.append(\n",
    "    {'mode': 'Brute-force', 'precursor_tol_mass': 20,\n",
    "     'precursor_tol_mode': 'ppm', 'num_list': -1, 'num_probe': -1,\n",
    "     'ssms': len(reader.read_mztab_ssms(\n",
    "         os.path.join(hp_dir, '..', 'brute_force', 'bf_std.mztab'))),\n",
    "     'time': extract_time_from_log(\n",
    "         os.path.join(hp_dir, '..', 'brute_force', 'bf_std.log')),\n",
    "     'gpu': False\n",
    "    }, ignore_index=True)\n",
    "\n",
    "hyperparameters = hyperparameters.append(\n",
    "    {'mode': 'Brute-force', 'precursor_tol_mass': 300,\n",
    "     'precursor_tol_mode': 'Da', 'num_list': -1, 'num_probe': -1,\n",
    "     'ssms': len(reader.read_mztab_ssms(\n",
    "         os.path.join(hp_dir, '..', 'brute_force', 'bf_oms_shifted.mztab'))),\n",
    "     'time': extract_time_from_log(\n",
    "         os.path.join(hp_dir, '..', 'brute_force', 'bf_oms_shifted.log')),\n",
    "     'gpu': False\n",
    "    }, ignore_index=True)\n",
    "\n",
    "hyperparameters = (hyperparameters[\n",
    "    ['mode', 'precursor_tol_mass', 'precursor_tol_mode', 'num_list',\n",
    "     'num_probe', 'ssms', 'time', 'gpu']]\n",
    "    .sort_values(['precursor_tol_mass', 'num_list', 'num_probe', 'gpu'])\n",
    "    .reset_index(drop=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "hyperparameters['time'] = hyperparameters['time'] / 60"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_pareto_frontier(arr):\n",
    "    # Sort by the first column.\n",
    "    arr_sorted = arr[arr[:, 0].argsort()]\n",
    "    # Iteratively add points to the pareto frontier.\n",
    "    pareto_idx = [0]\n",
    "    for i in range(1, arr_sorted.shape[0]):\n",
    "        if (arr_sorted[i, 1] > arr_sorted[pareto_idx[-1], 1] and\n",
    "                arr_sorted[i, 0] - arr_sorted[pareto_idx[-1], 0] > 0.1):\n",
    "            pareto_idx.append(i)\n",
    "    return arr_sorted[pareto_idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "hp_cpu = hyperparameters[(hyperparameters['mode'] == 'ANN-SoLo') &\n",
    "                         ~(hyperparameters['gpu'])]\n",
    "hp_cpu_pareto = get_pareto_frontier(hp_cpu[['time', 'ssms']].values)\n",
    "\n",
    "hp_gpu = hyperparameters[(hyperparameters['mode'] == 'ANN-SoLo') &\n",
    "                         (hyperparameters['gpu'])]\n",
    "hp_gpu_pareto = get_pareto_frontier(hp_gpu[['time', 'ssms']].values)\n",
    "\n",
    "hp_bf = hyperparameters[hyperparameters['mode'] == 'Brute-force']\n",
    "\n",
    "fdr = 0.01"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 504x311.496 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "width = 7\n",
    "height = width / 1.618    # golden ratio\n",
    "fig, ax = plt.subplots(figsize=(width, height))\n",
    "\n",
    "# ANN-SoLo CPU performance.\n",
    "ax.plot(hp_cpu_pareto[:, 0], hp_cpu_pareto[:, 1], '--o', color='C1',\n",
    "        label='ANN CPU 300 Da')\n",
    "\n",
    "# ANN-SoLo GPU performance.\n",
    "ax.plot(hp_gpu_pareto[:, 0], hp_gpu_pareto[:, 1], '--o', color='C0',\n",
    "        label='ANN GPU 300 Da')\n",
    "\n",
    "# Brute-force closed & open performance.\n",
    "for i, (_, row) in enumerate(hp_bf.iterrows(), 2):\n",
    "    ax.scatter(row['time'], row['ssms'], marker='s', color=f'C{i}',\n",
    "               label=f'{row[\"mode\"]} {row[\"precursor_tol_mass\"]} '\\\n",
    "                     f'{row[\"precursor_tol_mode\"]}')\n",
    "\n",
    "ax.set_xlabel('Total runtime (min)')\n",
    "fdr = 0.01\n",
    "ax.set_ylabel(f'Number of SSMs (FDR={fdr:.0%})')\n",
    "\n",
    "ax.set_xscale('log')\n",
    "\n",
    "ax.legend(loc='lower right', title='Search mode')\n",
    "\n",
    "sns.despine()\n",
    "\n",
    "# plt.savefig('ann_hyperparameters.pdf', dpi=300, bbox_inches='tight')\n",
    "plt.show()\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_spectra = len(list(reader.read_mgf(mgf_filename)))\n",
    "num_id = (hyperparameters[(hyperparameters['mode'] == 'Brute-force') &\n",
    "                          (hyperparameters['precursor_tol_mass'] == 300)]\n",
    "          ['ssms'].iat[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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8j7ZmdkVuf2g/lvCSqPREB8fj4ODA6NGjSIzM5PAwEASExOZPn0648ePx9LSEoAhQ4Ywe/Zszp07pz4OnD17NpMnT8bHx4dvv/2WSZMmUa1aNezt7Vm6dCnz58/H09OTlJQUJkyYoNdbyrNkyRISExMJDAykW7duar3KTz75BDc3NyIiIliyZAn29vbs27ePM2fOEBUVhYWFBS+//DIAw4cP55NPPiEgIAATExOsra3x8fFBo9EYHMeMGTPYt28fR44cUdtatmwJ5P4hsGDBAqZPn46bmxuxsbEEBAQU+b0OCQlh7969XLx4kWnTpuk9xrWxsWH27NmEhYVhb2/P66+/TkxMDGvWrOHatWts2rQJR0dH9bPWrVuXHTt2APDpp58yfvz4+/mxCyEeA/IepahQivr/YdKkSTRp0kQtDC2EECCPXkUFMm3aNL3/3m379u2cOnWKrVu3cvbs2UcdmhCiDJMepRBCCFEE6VEKIYQQRZBEKYQQQhShXM56leohQgghiouMUQohhBBFkEevQgghRBHK5aPXx5HUoyzcmTNnWL58OdWrVychIYGEhARmzJiBi4sLUHQ9yujoaKZPn46TkxORkZGMGDECHx+ffPc4evQoy5Yt4+LFi7zyyiukpKSQkpJCx44d5b1KISq60lqN/VEp7uohJenIkSNKgwYN1O2MjAzl2WefVQ4ePPjA1/xv9ZD71bFjR+XatWuKoihKUlLSA1/nYRw8eFD5888/1e3Ro0crc+fOVRRFUdLT05XmzZurFUA2btyojBx5p6LLiBEjlC1btiiKoijXrl1TWrRooWRkZBR4ny1btiivvPKKup2UlKQMHDhQvZcQomKSHmUZJvUoc91dtDozM5OIiAjat28PFF2PUlEUDh06xOzZs4Hcih92dnYcPnyYF1544Z7ff1tbWwICAujcuTMDBw7E3d2dyZMnk5OTg62tLbdu3WLq1KnqUoJCiPJJEuV92vTOT6QnZajb5nZm9FrUudiun5WVxaxZs8jMzOTIkSO89dZb1K1bF4D58+fz4osv0rp1a0aOHMnq1avp06cPP/30k3r+xIkTWbVqFQBPPvkk3bt359ixY0yfPh2AxMRExo4dy44dO7C3tyc6OpquXbty8ODBfL/w33nnHbZv3867776Lh4cHiYmJjBo1ik2bNlG1alWuX79Or1692L17N0OHDuXKlSuEhYURGBhISEgIMTExLF++nNu3bzNnzhwgd/3YyMhIrK2tDY4jz/Lly9m5cydt27bF19cXgBs3buDs7Kwe4+joiJGREeHh4SiKgrGxsVoaC8DZ2ZkbN24Y/POoWbMmFhYWnD17Fnd3d5577jm6du0KwIoVK1i3bh1Dhw41+HpCiMePJMr7lJ6UQWpCWoldX+pRFl6PcsSIEQwdOpSJEyeqi74XRCmBidx55bY0Gg0TJ07ExsaGq1ev4urqWuz3EkKULZIoyzCpR5krJSUFGxsbAIyNjencuTMffvghkydPLrIepaIo5OTkEB8fr/YqY2Nj8fDwMDjGkJAQ0tLSqFu3LqdPn2b69OkcOHAAGxsbdaKUEKJ8k9dDyjipRwmzZs0iJCRE3b506ZLa6yyqHqWDgwNt2rTh0KFDAISGhpKUlESrVq3u+VkBkpOTmTZtGq+99jutB40AABcWSURBVBru7u4kJCRgamqKhYUFAOHh4QZdRwjxeDP+6KOPPirtIErSkiVLGD16dLFd79LBq2g0GkwtTDG1MMXczozaL3s99HWDgoJYuXIlV69eJTY2lsOHD7Nt2zasra3VGodz587lwoULREdH07hxY0xNTQFwd3fniy++4OLFi1hYWPDLL7+QkpJCy5YtsbOzY8OGDZw4cQKtVkvLli1p2LAhS5cu5dy5c+zZs4ehQ4fq1WTMs2TJEv766y9iY2OxtrbmqaeeokGDBixbtozTp0+ze/duJk+ezBNPPMG+ffvYunUr4eHhmJmZ8dRTTwHQoEEDjh8/zv79+/n9998xMjLCz88PCwsLg+PIzMxk5cqVnD9/nl9++YWrV68SEBCAnZ0dJiYm1KlThwULFnDixAkuXLhAQEAA1tbWADRq1IhvvvmGo0ePsnPnTqZMmUK1atXy3ePo0aOsXbuW0NBQIiMj2blzJz/99BOdOnVi2LBhQO6j5uDgYNavX8+lS5e4dOkSwcHBuLq68sQTTzz0/wNCiLJJVuYRQgghiiCPXoUQQogiSKIUQgghiiCJUgghhCiCJEohhBCiCJIohRBCiCJIohRCCCGKIIlSCCGEKMJjkSh/+OEHvQoSQgghxKNS5hNlVlYWPj4+mJubl3YoQgghKqBHmih1Oh3r16+nadOmHD16VG9fdHQ0I0eOxN/fX617CLnVNApackwIIYR4FB5p9ZBt27bh7u6OlZVVvn0BAQF07NgRPz8/QkND6d+/PwcOHECr1T7KEIUQQgg9jzRR+vn5FdiemJj4UJXo8wQGBrJkyZJ87bVq1XqwgIUQQpRbhq4DXibqUYaHh/+vvbuPyynNHzj+kSlTU0PUJJSm0jTseLUx0sbOZGKzalgPq2hJ7ewgD5llYmtItXnaoZjRDiZshlkUNjKNpzVk0pLh1YgREyVUeq6p+07X749+ndfcPdzEILner5c/zjnXOff1dZ2r6z7nPuf6as1Ev3//fioqKti9ezfjx49v8TizZ8/+RTOFPKrnYUL29h5je48P2n+M7T0+kDE+bm1ioLwfDw8PPDw8nnY1JEmSpOdQm3jqtUePHkom+gatzUQvSZIkSY9DmxgoHzUTvSRJkiQ9Lh1DQ0NDn9SHZWZmEhMTw3fffUdRURFlZWW88cYbwINnon/WODk5Pe0qPHbtPcb2Hh+0/xjbe3wgY3ycOgghxFP5ZEmSJEl6BrSJW6+SJEmS1FbJgVKSJEmStJADpSRJkiRpIQdKSZIkSdLimZhwoK1wcHDg5ZdfVpYrKioICAjA398fgKioKG7cuAGAlZUVc+bMAeDChQts2bKFrl27YmJiwvTp04H612DCwsLYtGkTHTp0eMLRNE9bjKdPn2b27NkamVy2bNmCtbU1OTk5fPzxx5ibm6NWqwkJCQHqpyecMWMGmzdvbjMZYE6cOMHmzZvp06cPubm5DBkyBG9vb9RqNeHh4ajVasrKynBxcWHSpEkAHD9+nP3799OpUyccHR2V6RjPnDnDzp07Wbly5dMMqYmWYkxISGDlypUacygnJyejr6//TJ2np06d4osvvsDS0pLbt28zbtw4hgwZArSPfggtx/gs98O6ujp27tzJmjVrWLt2rcZTrC21G9Sfo3v27MHU1JSqqiqWLl2KoaEhpaWlLFmyBAsLCwoKCggPD0dXVxeVSsW0adOIjo7GxMTk0SsupAcWHh6usezr6yvu3LkjhBAiOTlZ+Pj4KNu8vb3F4cOHhRBCBAQEiIsXLwohhBg9erQoLy8XdXV1wt/fX1y7du0J1f7BaIsxNTVVxMfHN7tfZGSkOHTokBBCiBkzZohLly4JIYQICgoSqampj7HGrefs7CyOHTsmhBCivLxcvP766+L69esiNjZWBAUFCSGEqKmpEW5ubiIzM1MIIcSYMWNEaWmpuHfvnhg+fLgQQojq6mrh7e0t7t69+1Ti0KalGOPj41tsj2flPC0pKREODg5KncrLy4Wzs7PIz89vN/1QW4zPcj+Mj48Xx48fF66urhr10dZu+fn5wtnZWZSVlQkhhFi9erWIjIwUQgixdetWsXnzZiGEEBEREeLIkSNCCCGioqLEnj17frF6y1uvrdDw7Qzg+++/x8TEhFdeeQWAPXv2aCSXHjZsGAkJCQAYGhpSVFREbW0tNTU16Orq8uWXXzJo0CBeffXVJxrD/WiLEeDw4cMsW7aM0NBQkpOTlfUNMUL9t9eXXnqJEydO0KlTpzb3fpeZmRkFBQUAFBUVIf7/Damft6Genh5Dhgxp0oaVlZXo6uoC9ZPwT548WWOO4raipRgBdu/ezfLlywkNDSU1NVVZ/6ycpzk5OahUKqysrID6enft2pWvv/663fRDbTHCs9sPx44d2+xEMtra7cCBA7zxxhsYGRkB8M4772i0aXFxMVB/nhsZGXH58mUyMzMZM2bML1Zveev1IW3ZsoWpU6cqy7m5ubi7uyvLJiYm5OTkADBv3jzWr19PcnIyCxYsoLi4mKSkJIKDg1m8eDEdO3Zk5syZmJqaPvE4tGkco7m5ORMmTMDV1ZWffvqJyZMnU1NTw7vvvouvry/R0dEsWbKEsWPHYmxszIIFC4iIiCA0NJS6ujqmTZvWJv4gRUdHExgYSHp6OhkZGSxbtgxLS0tyc3M1btOYmJiQkZEB1KeB27RpEwARERFcvHiR7OxsXF1dWbx4Mfr6+syZM6fZFHJPQ0sxlpaWYmVlhaOjI8XFxYwdO5ZVq1YxcODAZ+Y8tba2xtjYmFOnTuHi4kJubi43btwgLy+v3fRDbTEOHTq0XfTDn9PWbo37pampKWVlZZSWluLp6Ul0dDRhYWG89tprODo64ufnx6JFi4iMjKS6upqxY8fi4ODwaBX8xa5NnyO3b98WU6dO1Vjn4eEh9u7dqyzv2bNHeHp6Nrt/QECA+P7778XEiRPFrVu3RHp6uvjggw8eZ5VbrbkYG/v888+Fn59fs9vCwsLE0aNHRWBgoEhPTxd5eXli0qRJj6GmrVNdXS3c3NzE8ePHhRBC3LlzR3h6eopbt26JX//61+J///ufUvaTTz4R06dPb3IMtVotfHx8RF5enhg1apSorKwUiYmJYvXq1U8sDm20xdhYWFiY+Oijj5o9Tls+T7OyssTixYvFqlWrxGeffSb8/PzE+vXr21U/bCnGxp7Fftj41qu2dgsPDxd/+9vflG15eXnCzs5OlJSUNDnupk2bRFxcnFi1apVITEwUlZWVwtPTU9TV1T1SfeWt14ewfft2Jk+erLGuV69eFBYWKsstTeqemJiItbU1ffv2pbCwkO7du9OvXz8yMzMfe71bo7kYs7OzNW7h6enp8dNPPzXZ9+zZs5SVleHq6sqlS5f41a9+hbm5Ofn5+Y+93vfzww8/KN/KAV555RWMjY05ePDgA7fhpk2b8PDwQFdXF0NDQwwMDOjXrx8XL158YnFooy3GH3/8UaNsS23Y1s9TGxsbli5dyvz58/nLX/5CWVkZtra27aofthRje+iHjWlrt8bbCgoKMDIyonPnzhrHuHHjBikpKUyaNEmJ18DAAAMDA42EGw9DDpStVF1dTUpKCu+8847G+jFjxiiTugMcO3asSaLqoqIiduzYQUBAAAAGBgbU1NRQUFCAubn5Y6/7g2opxn/+859cuXJFWU5JSeE3v/mNRhmVSsXHH3/MokWLgPrbtQUFBdTU1GBgYPD4K38fPXv2REdHh2vXrgGgVqu5fv06PXr00GhDlUrFyZMn+cMf/qCx/7Vr1zhz5gx//OMf6dy5MxUVFQBtqg21xRgREUFJSQlQ/wRiamoqLi4uGvs/C+fp0qVLqa2tBeq/GJSXlzNs2LB21Q9birE99MPGtLXbyJEjuXDhAuXl5QAcOXKkSb8UQhAeHk5wcDA6OjpKvACVlZVNBtXWknO9ttKXX35JdXU1vr6+GuuFEKxZs4bc3FyEEFhaWjJv3jyNMgsWLGDixIkMHDgQqD8ZDh06hFqtZsqUKcoE8U9bSzEmJSWRkJCAjY0NRUVFGBsbM3/+fI1XDdasWYOtrS2enp5A/SP5cXFx6Orq8rvf/Y633nrrSYbSrMOHD7Njxw5sbGzIycnBzs6OwMBA1Go1YWFh3Lt3j9LSUlxcXDSuqoUQ+Pv7s2TJEnr37g3Azp07uXTpEqWlpQQGBmJhYfG0wtLQUozbtm0jJSWF3r17c+vWLezt7Zk5c6bGvs/CeRoUFERJSQk9e/aktLSU2bNnY2Vl1a76YUsxPsv9MDMzk127drF3716cnZ2V15bu125JSUns27cPU1NTKisrCQ8Px9DQUNm+c+dOioqKlFd+bty4QXR0NJ07d+b1119nwoQJj1RvOVBKkiRJkhby1qskSZIkaSEHSkmSJEnSQg6UkiRJkqSFHCglSZIkSQs5UEqSJEmSFnIKO6nduXPnDsuWLePWrVvKy9gODg7MmTNHyYySkJDAF198gYGBAWq1GiMjIyZOnIibmxvffvsta9as4fz580yfPr3J6wVQP41dXFwcgwYNYsmSJdja2j7pMDX8+c9/5vTp02zatIm+ffsyc+ZM0tLSOHLkSLMv3Df49ttvuXnzJuPHjyc0NJRDhw5RW1uLnZ2dUqampgZra2vmzJlDUFAQaWlp9OnTB2NjY6qqqrC2tmbGjBlYW1sDkJeX16RcZWUltbW1BAUFNXlv80Hl5+ezaNEiVCoVcXFxGtv+85//sHXrVnR1dQkLC1PqX1NTw8SJE9m8eTPGxsZA/cvs//rXv5g1a5bGKxWS1KJHmtdHktogX19fsXz5cmX59u3bwsXFRWRlZQkhhDhz5owYOHCgxpRu0dHRIjAwUFnOyckR/fv3F4MGDRJVVVUaxy8tLRWDBw8WdnZ2jzmS1mk8LZidnZ3Iyclpsfz58+eFj4+PUKlUyrqgoCAxY8YMjXI5OTlKVpWG4zZkqBCifrqxN998U5w7d05jv8bl1q1bJwYMGCDKy8tbHdulS5eEr6+v+OCDDzSyTAghREVFhXBxcRGlpaUiJSVFTJkyRdkWExOjZJf4uYSEBLFw4cJW10N6Pslbr1K7c+7cOQYPHqwsm5mZMWvWLCX7wLlz5+jduzfdu3dXyvj4+CgvoDdoyGbQkKmgwb///W9GjBjxmGr/5Hz00Ue8//77SjaUlpiYmDBt2rQWt48ZM4ZRo0axaNEi6urqWiw3YsQIysvLm0yj9yC6devGZ599pmTT+Lkff/wRc3NzXn75ZRwdHblw4QJQP1tScnJyk6kYG+qcnp7OmTNnWl0X6fkjB0qp3bGwsGDr1q3cvXtXWefl5aWkC7OwsCAzM5OvvvpKmTOza9euTf6g6uvr4+XlxdatW5UBoLa2ltTUVGUe1ZasWbMGFxcXIiIiWLBgAe7u7owePRqAy5cvM2XKFHx8fPD29ubUqVPKftXV1YSHh+Pl5YWPjw+zZs0iKysLgP/+979MnToVX19fvL292bdv30P/H129epUrV64wYMAAreVOnz7Nxo0bee2117SWmzBhAteuXdM6V+q9e/fo2LGjRtq2B2ViYtLibdIXXnhBaZ+6ujqlXFRUFAEBAc1+EejQoQNOTk7s37+/1XWRnj/yN0qp3QkNDWXu3Lm89dZbDBkyBHd3d37/+98rf0Dd3Nzw9PRk7ty5WFhYMGLECMaNG4eNjU2TY/n4+BAbG8uRI0cYPnw4SUlJjBw58r51mDdvHnfu3OHkyZPs3r0bAwMDIiMjqaiowM/Pj5CQEEaOHEl2djbjxo0jKSkJMzMzVqxYQWFhITt27KBDhw6sWLGCkydPYmtrS1VVFStXrsTMzAyVSsXo0aNxdHR8qGnzMjIyMDU1RV9fv8m2s2fP8qc//QmAsrIy3Nzc7nu8hjpcv36dfv36NdkuhODw4cMEBwdjZmamrG/4nObY29sTHBx838+2tramuLiYwsJCvvvuO5ycnMjMzCQvL09r3S0tLTl48OB9jy9JcqCU2p0BAwZw9OhRDh06RHJyMiEhIcTExLBt2zZMTU3p2LEjy5cv57333mP//v0kJSURGxvLhx9+iJ+fn8axTE1N8fDwIDY2luHDh5OYmMinn37KN99880B1GTJkiDInZUhICImJidTW1iqDrZWVFfb29hw4cABfX1/i4+NZu3YtHTp0AOof0ikrKwPqB47IyEjy8/N54YUXKCgoICMj46EGysLCwhZzZw4YMID169cD9VeUaWlp9z1ewxVdQ70bREVFsWXLFq5cucKrr77Khg0bNLY3fijnYejp6bFy5UqWLl3Kiy++SEhICB9++CELFy4kOzubtWvXolKpmDBhgsYcpy+99JJGVgpJaokcKKV2SU9Pj1GjRjFq1Cjy8vLw9vZm+/btzJ07VyljY2PD3LlzmTt3LuvWrWP16tVMnjyZTp06aRzLz88PDw8PNmzYgIODQ6uelGycteD27duoVCqNK6ni4mIqKyspLi6mpqaGrl27Ktu6detGt27dAJg+fTru7u5ER0cD9VdjzaVXehBCiCaDWnOcnJxwcnK6b7nc3FyAJr8hBgYG4ubmRlZWFhMmTODzzz9v9iniRzVw4EDlN+bDhw/Tq1cv7O3t8fLyYv78+djZ2eHp6UliYqLy5LOOjo5GuipJaokcKKV257333mPjxo3Kco8ePXBwcFBSYn311VdUVFQwfvx4pczIkSP55JNPqKmpaTJQ9unTh6FDh7J+/XqOHj36SHUzNzenS5cuGldS1dXV3Lt3D319fTp16qSRO6+0tJSSkhKMjIy4fv06w4YNU7ap1eqHroeJiQmVlZUPvX9ju3btwtraGnt7+2a329ra4uvry/bt23n//feVVE+/xK3Xn1Or1Xz66adK+2dmZtK/f3/09PTo3r072dnZ9O/fH4CKigpMTExadXzp+SQf5pHanatXr2r89nTnzh3Onj2Ls7MzAFVVVezcuVNjoDh48CD9+vVTrjYa++tf/0p4eLjG1d7DePvtt1Gr1aSkpAD1tyznz59Peno6Ojo6jBs3jvj4eOVKJyoqivT0dLp06UKXLl04e/asEtPly5cfuh729vZKfsJHtW/fPpKSkli2bJnWq1RfX19qa2uJj49X1sXFxbX4r7WDJMC2bdtwd3dXBsCePXuSlZWFSqUiLy9PI99kbm4uffv2bfVnSM+fjqGhoaFPuxKS9EsyMDBg9+7dJCQkkJCQwN69e/H19eXdd98FwNDQkJycHDZs2EBiYiI7duxApVIRHh5O586duXDhAsHBwWRkZJCTk8Pbb7+Nqamp8uRnUlIS69ato6ioiLS0NN58880mt1hjYmLYv38/V65c4erVq7i6ugL1t4RdXFyIjo5m165dxMfH89vf/lZ5Inbw4MGcP3+emJgY4uPj6dWrF/7+/ujo6GBvb09MTAxff/01ly9fprq6mrS0NCwtLQkPD+eHH34gIyMDGxsbFi5cyM2bNzl//jyDBg2iS5cuGvUzMTHhwIED2NvbKxMShIaGcuzYMW7evMk333zD0KFDNX7HzMvLY+bMmdy8eZMrV65w8OBBtm3bRnl5OcuXL1cGncbl1Go1Dg4OvPjii1RVVREbG8vFixdxd3d/4DatrKzE39+ftLQ0bt68yYkTJ+jVqxc9e/ZUyhQXF7NixQr+/ve/88IL9TfLrK2tiYiIID4+Hi8vL43JDv7xj3/g7+/fZnKISm2XzEcpSc+p06dPs2HDBjZu3IiOzvN1cyk5OZmkpCTl915J0ub56h2SJCmcnJzw8vLSuBX6PLh79y5paWlERkY+7apIzwh5RSlJkiRJWsgrSkmSJEnSQg6UkiRJkqSFHCglSZIkSQs5UEqSJEmSFnKglCRJkiQt5EApSZIkSVrIgVKSJEmStPg/cgEgRLdbewoAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 504x311.496 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "width = 7\n",
    "height = width / 1.618    # golden ratio\n",
    "fig, ax = plt.subplots(figsize=(width, height))\n",
    "\n",
    "# ANN-SoLo CPU performance.\n",
    "ax.plot(hp_cpu_pareto[:, 1] / num_id, num_spectra / hp_cpu_pareto[:, 0],\n",
    "        '--o', color='C1', label='ANN CPU 300 Da')\n",
    "\n",
    "# ANN-SoLo GPU performance.\n",
    "ax.plot(hp_gpu_pareto[:, 1] / num_id, num_spectra / hp_gpu_pareto[:, 0],\n",
    "        '--o', color='C0', label='ANN GPU 300 Da')\n",
    "\n",
    "# Brute-force closed & open timing.\n",
    "for i, (_, row) in enumerate(hp_bf.iterrows(), 2):\n",
    "    ax.scatter(row['ssms'] / num_id, num_spectra / row['time'],\n",
    "               marker='s', color=f'C{i}',\n",
    "               label=f'{row[\"mode\"]} {row[\"precursor_tol_mass\"]} '\\\n",
    "                     f'{row[\"precursor_tol_mode\"]}')\n",
    "\n",
    "ax.set_xlabel(f'SSM recall (FDR={fdr:.0%})')\n",
    "ax.set_ylabel('Spectra per minute')\n",
    "\n",
    "ax.set_yscale('log')\n",
    "ax.set_ylim(10, 10000)\n",
    "               \n",
    "ax.xaxis.set_major_formatter(mticker.PercentFormatter(1, 0))\n",
    "\n",
    "ax.legend(loc='lower left', title='Search mode')\n",
    "\n",
    "sns.despine()\n",
    "\n",
    "plt.savefig('ann_hyperparameters.pdf', dpi=300, bbox_inches='tight')\n",
    "plt.show()\n",
    "plt.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# du -ch --block-size=1M human_yeast_targetdecoy_1* | grep total\n",
    "build_sizes = {64: 3621, 256: 3624, 1024: 3638, 4096: 3677, 16384: 3777}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f84c8f3ab7a649f798286058ade89c27",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "HBox(children=(IntProgress(value=0, description='Files processed', max=7, style=ProgressStyle(description_widt…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "pattern_build_log = re.compile('^num_list_(\\d+).log$')\n",
    "\n",
    "build_runtimes = []\n",
    "for filename in tqdm.tqdm(os.listdir(build_dir), 'Files processed',\n",
    "                          unit='files'):\n",
    "    match_build_log = pattern_build_log.match(filename)\n",
    "    if match_build_log is not None:\n",
    "        num_list = int(match_build_log.group(1))\n",
    "        build_runtimes.append((\n",
    "            num_list,\n",
    "            extract_time_from_log(os.path.join(build_dir, filename)),\n",
    "            build_sizes[num_list]))\n",
    "\n",
    "build_df = pd.DataFrame.from_records(\n",
    "    build_runtimes, columns=['num_list', 'build_time', 'size'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "build_df['build_time'] = build_df['build_time'] / 60\n",
    "build_df['size'] = build_df['size'] / 1024\n",
    "hyperparameters = hyperparameters.merge(build_df, how='outer', on='num_list')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "table_latex = hyperparameters.copy()\n",
    "# use pretty column names\n",
    "table_latex['Search mode'] = (\n",
    "    table_latex['mode'] + ' ' +\n",
    "    table_latex['precursor_tol_mass'].map(str) + ' ' +\n",
    "    table_latex['precursor_tol_mode'])\n",
    "table_latex['num\\_list'] = table_latex['num_list']\n",
    "table_latex['Build time (\\si{\\minute})'] = table_latex['build_time'].map(\n",
    "    '{:,.1f}'.format)\n",
    "table_latex['Index size (\\si{\\giga\\\\byte})'] = table_latex['size'].map(\n",
    "    '{:,.2f}'.format)\n",
    "table_latex['{num\\_probe}'] = table_latex['num_probe']\n",
    "table_latex['GPU'] = table_latex['gpu'].map({True: '{\\\\boxedsymbols ✓}',\n",
    "                                             False: '{\\\\boxedsymbols ✗}'})\n",
    "table_latex['\\#~SSMs'] = table_latex['ssms']\n",
    "table_latex['Search time (\\si{\\minute})'] = table_latex['time'].map(\n",
    "    '{:,.1f}'.format)\n",
    "# Remove duplicated column values for pretty LaTeX output.\n",
    "dup_columns = ['Search mode', 'num\\_list', 'Build time (\\si{\\minute})',\n",
    "               'Index size (\\si{\\giga\\\\byte})']\n",
    "table_latex.loc[table_latex.duplicated(dup_columns), dup_columns] = ''\n",
    "table_latex.loc[[False, True] * (len(table_latex) // 2), '{num\\_probe}'] = ''\n",
    "table_latex.loc[table_latex['Search mode'].str.contains('Brute-force'),\n",
    "                ['num\\_list', '{num\\_probe}', 'Build time (\\si{\\minute})',\n",
    "                 'Index size (\\si{\\giga\\\\byte})', 'GPU']] = ''\n",
    "table_latex = table_latex.drop([\n",
    "    'mode', 'precursor_tol_mass', 'precursor_tol_mode', 'num_list',\n",
    "    'num_probe', 'ssms', 'time', 'build_time', 'size', 'gpu'], axis=1)\n",
    "table_latex.to_latex(\n",
    "    buf='tab-ann_hyperparameters.tex',\n",
    "    index=False,\n",
    "    column_format='@{\\extracolsep{\\\\fill}}lrR{1cm}R{1cm}rrrR{1cm}',\n",
    "    longtable=True, escape=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "logging.shutdown()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
